/**
 * 
 */
package io.gitee.falllee.dao.core;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import io.gitee.falllee.dao.config.DaoConfigContextHolder;
import io.gitee.falllee.dao.model.DaoConst;
import io.gitee.falllee.dao.util.DaoUtils;

/**
 * @author batty
 * @date 2022-11-28
 *
 */
public class ExtendBuilder<T> extends HeaderBuilder {
    
    private Class<T> clazz;
    
    private Dao dao;
    
    private String[] uniqueKey;
    
    private Boolean allowUpdateNullData;
    
    private String updateFields;
    
    private ConcurrentHashMap<String, Method> methodMap = new ConcurrentHashMap<>();
    
    ExtendBuilder(Dao parent, Class<T> clazz) {
        super(parent.getDaoConfig());
        this.dao = parent;
        this.clazz = clazz;
    }
    
    /**
     * 增加唯一键（非主键）
     * 
     * @param uniqueKey
     * @return
     */
    public ExtendBuilder<T> uniqueKey(String... uniqueKey) {
        this.uniqueKey = uniqueKey;
        return this;
    }

    /**
     * 是否允许更新空值，默认不更新
     * 
     * @return
     */
    public ExtendBuilder<T> allowUpdateNullData() {
        this.allowUpdateNullData = true;
        return this;
    }

    /**
     * 更新字段
     * 
     * @param updateFields
     * @return
     */
    public ExtendBuilder<T> updateFields(String updateFields) {
        this.updateFields = updateFields;
        return this;
    }

    @Transactional(rollbackFor = Exception.class)
    public Boolean saveData(T t) {
        Object keyValue = restoreData(t);
        if (ObjectUtils.isEmpty(keyValue)) {
            return dao.insertBuilder(clazz).insert(t);
        } else {
            if (StringUtils.hasLength(updateFields)) {
                return dao.updateBuilder(clazz).updateFields(updateFields).update(t);
            }
            return Boolean.TRUE.equals(allowUpdateNullData) ? dao.updateBuilder(clazz).allowUpdateNullData().update(t)
                    : dao.updateBuilder(clazz).update(t);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public Object restoreData(T t) {
        List<Object> keyValueList = restoreDataList(Collections.singletonList(t));
        return CollectionUtils.isEmpty(keyValueList) ? null : keyValueList.get(0);
    }

    @Transactional(rollbackFor = Exception.class)
    public Boolean saveDataList(List<T> list) {
        return saveDataList(list, false);
    }

    @Transactional(rollbackFor = Exception.class)
    public Boolean saveDataList(List<T> list, Boolean allowUpdateNullData) {
        Boolean bl = true;
        List<Object> keyValueList = restoreDataList(list);
        List<String> keyValueStrList = keyValueList.stream().map(Object::toString).collect(Collectors.toList());
        List<T> insertList = list.stream().filter(p -> isEmpty(p, keyValueStrList)).collect(Collectors.toList());
        List<T> updateList = list.stream().filter(p -> isNotEmpty(p, keyValueStrList)).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(insertList)) {
            bl = dao.insertBuilder(clazz).insertList(insertList);
        }
        if (Boolean.TRUE.equals(bl) && !CollectionUtils.isEmpty(updateList)) {
            if (StringUtils.hasLength(updateFields)) {
                return dao.updateBuilder(clazz).updateFields(updateFields).updateBatch(updateList);
            }
            return Boolean.TRUE.equals(allowUpdateNullData)
                    ? dao.updateBuilder(clazz).allowUpdateNullData().updateBatch(updateList)
                    : dao.updateBuilder(clazz).updateBatch(updateList);
        }
        return bl;
    }

    private boolean isEmpty(T t, List<String> keyValueList) {
        Object keyValue = DaoUtils.getKeyValue(t);
        return keyValue == null || DaoConst.STRING_ZERO.equals(keyValue.toString())
                || !keyValueList.contains(keyValue.toString());
    }

    private boolean isNotEmpty(T t, List<String> keyValueList) {
        return !isEmpty(t, keyValueList);
    }

    @Transactional(rollbackFor = Exception.class)
    public List<Object> restoreDataList(List<T> list) {
        boolean bl = DaoConfigContextHolder.getIncludeDisable();
        if (!bl) {
            DaoConfigContextHolder.setIncludeDisable(true);
        }
        try {
            // 如果存在唯一键，先按唯一键查询
            if (!ObjectUtils.isEmpty(uniqueKey)) {
                setFixedFieldValue(list);
                setCreateAndUpdateUser(list);
                List<T> dbList = listDataByUniqueKey(list);
                List<Object> deleteKeyValueList = new ArrayList<>();
                for (T db : dbList) {
                    Map<String, Object> uniqueKeyValueMap = new HashMap<>(16);
                    for (String key : uniqueKey) {
                        Method method = getMethod(key);
                        Object obj = DaoUtils.getValue(db, method);
                        uniqueKeyValueMap.put(key, obj);
                    }
                    List<T> subList = list.stream().filter(p -> {
                        for (String key : uniqueKey) {
                            Method method = getMethod(key);
                            Object obj = DaoUtils.getValue(p, method);
                            if (!uniqueKeyValueMap.get(key).toString().equals(obj.toString())) {
                                return false;
                            }
                        }
                        return true;
                    }).collect(Collectors.toList());
                    for (T t : subList) {
                        Object keyValue = DaoUtils.getKeyValue(t);
                        Object dbKeyValue = DaoUtils.getKeyValue(db);
                        // 如果主键为空，则回写主键，如果主键和数据库主键不一致，则将数据库硬删除
                        if (keyValue == null) {
                            DaoUtils.setKeyValue(t, dbKeyValue);
                        } else if (!keyValue.toString().equals(dbKeyValue.toString())
                                && !deleteKeyValueList.contains(dbKeyValue)) {
                            deleteKeyValueList.add(dbKeyValue);
                        }
                    }
                }
                // 需要硬删除
                if (!CollectionUtils.isEmpty(deleteKeyValueList)) {
                    dao.deleteBuilder(clazz).hardDelete().deleteByKeyList(deleteKeyValueList);
                }
            }
            List<Object> keyValueList = list.stream().map(DaoUtils::getKeyValue)
                    .filter(p -> !ObjectUtils.isEmpty(p)).collect(Collectors.toList());
            List<T> result = dao.selectBuilder(clazz).selectByKeyList(keyValueList);
            if (!CollectionUtils.isEmpty(result)) {
                keyValueList = DaoUtils.getKeyValueList(result);
                dao.restoreBuilder(clazz).restoreByKeyList(keyValueList);
                return keyValueList;
            }
            return Collections.emptyList();
        } finally {
            if (!bl) {
                DaoConfigContextHolder.clearIncludeDisable();
            }
        }
    }

    private void setFixedFieldValue(List<T> list) {
        Map<String, Object> fixMap = getFixedFieldValue();
        for (String key : uniqueKey) {
            Object val = fixMap.get(key);
            if (ObjectUtils.isEmpty(val)) {
                continue;
            }
            Method getMethod = DaoUtils.getMethodByColumnValue(clazz, key);
            Method setMethod = DaoUtils.getSetMethodByColumnValue(clazz, key);
            for (T t : list) {
                Object obj = DaoUtils.getValue(t, getMethod);
                // 如果值为空可能是固定字段
                if (ObjectUtils.isEmpty(obj)) {
                    DaoUtils.setValue(t, setMethod, val);
                }
            }
        }
    }
    
    private void setCreateAndUpdateUser(List<T> list) {
        // 设置创建人、更新人（用于唯一键包含字段判断）
        String createUserFieldName = daoConfig.getCreateUser();
        Method createUserGetMethod = null;
        Method createUserSetMethod = null;
        if (StringUtils.hasLength(createUserFieldName)) {
            createUserGetMethod = DaoUtils.getMethodByColumnValue(clazz, createUserFieldName);
            createUserSetMethod = DaoUtils.getSetMethodByColumnValue(clazz, createUserFieldName);
        }
        String updateUserFieldName = daoConfig.getUpdateUser();
        Method updateUserGetMethod = null;
        Method updateUserSetMethod = null;
        if (StringUtils.hasLength(updateUserFieldName)) {
            updateUserGetMethod = DaoUtils.getMethodByColumnValue(clazz, updateUserFieldName);
            updateUserSetMethod = DaoUtils.getSetMethodByColumnValue(clazz, updateUserFieldName);
        }
        if (!StringUtils.hasLength(createUserFieldName) && !StringUtils.hasLength(updateUserFieldName)) {
            return;
        }
        Object userId = getCurrentUser();
        for (T t : list) {
            if (createUserGetMethod != null && createUserSetMethod != null) {
                Object obj = DaoUtils.getValue(t, createUserGetMethod);
                if (ObjectUtils.isEmpty(obj)) {
                    DaoUtils.setValue(t, createUserSetMethod, userId);
                }
            }
            if (updateUserGetMethod != null && updateUserSetMethod != null) {
                Object obj = DaoUtils.getValue(t, updateUserGetMethod);
                if (ObjectUtils.isEmpty(obj)) {
                    DaoUtils.setValue(t, updateUserSetMethod, userId);
                }
            }
        }
    }

    private List<T> listDataByUniqueKey(List<T> list) {
        if (ObjectUtils.isEmpty(uniqueKey)) {
            return Collections.emptyList();
        }
        List<T> result;
        if (uniqueKey.length > 1) {
            List<LinkedHashMap<String, Object>> paramList = new ArrayList<>();
            for (T t : list) {
                LinkedHashMap<String, Object> map = new LinkedHashMap<>();
                for (String key : uniqueKey) {
                    Method method = getMethod(key);
                    Object obj = DaoUtils.getValue(t, method);
                    map.put(key, obj);
                }
                paramList.add(map);
            }
            result = dao.selectBuilder(clazz).multiField().select(paramList);
        } else {
            String key = uniqueKey[0];
            Method method = getMethod(key);
            List<Object> fieldValueList = new ArrayList<>();
            for (T t : list) {
                Object obj = DaoUtils.getValue(t, method);
                fieldValueList.add(obj);
            }
            result = dao.selectBuilder(clazz).field(key).select(fieldValueList);
        }
        return result;
    }

    private Method getMethod(String key) {
        String methodKey = getMethodKey(key, clazz);
        return methodMap.computeIfAbsent(methodKey, p -> DaoUtils.getMethodByColumnValue(clazz, key));
    }

    private String getMethodKey(String key, Class<T> clazz) {
        return String.format("%s_%s", clazz.getSimpleName(), key);
    }
}
